MCP:AI 应用与外部工具协同的标准化协议解析


                                                                                                                                                <h1>MCP:AI 应用与外部工具协同的标准化协议解析</h1> 

MCP解析

文章涉及到的代码已经共享至 Github 仓库,欢迎访问交流:https://github.com/GerZhang/course-mcp-tech

前置知识

在了解 MCP 之前,我们需要对两个最基础的概念有个清楚的认知。因为这两个概念是 MCP 的底层技术原理,是我们深入了解 MCP 最重要的知识点。当掌握这两个基础概念后,MCP 的奥妙就会无所遁形。

stdio

stdio,全称为standard input and out(标准输入输出)。通常会在 C 语言中,以 stdio.h 的方式出现。顾名思义,它的功能就是提供一个信息的标准通道,使得程序可以有效和其他程序进行”打交道”。

stdio 通常包含三个标准流(standard streams):

名称 全称 缩写 作用简述 常见用途(C/C++)
stdin standard input 标准输入 程序用来接收输入的地方,通常是键盘 比如scanf()读取键盘输入内容
stdout standard output 标准输出 程序用来输出正常信息的地方,通常是屏幕 比如 printf打印的内容
stderr standard error 标准错误 程序用来输出错误信息或警告的地方,通常也是屏幕 用 fprintf(stderr, …)打印错误信息

在操作系统,以及多种编程语言中,都遵循 stdio 这个”标准三流”模型来实现程序与外部(终端、键盘、屏幕等)之间的通信。这也就意味着,我们所编写程序的所有输入输出,其底层都通过这三个默认通信管道进行数据交互。

场景举例

我们可以借助 node.js 来模拟一个标准的 stdio 场景。

1.构建一个 node 环境

  • mkdir stdio-demo npm init

2.创建 server.js,并通过 stdout 输出当前 server 的进程 id。

// server.js

process.stdout.write(process.pid + '\n')

3.在终端运行 server.js,查看输出内容

  • node server.js

img

当我们在终端输入命令 node server.js时,我们会在终端中看到 node 运行起来的 server 进程所拥有的进程 id。这是一个习以为常的场景,但是不知你有没有好奇过,为什么终端进程可以看到其他进程的数据信息呢?

答案就在 stdio 中。因为终端进程在开启一个新进程(node)的时候,顺手监听了新进程的标准输入(stdin)与标准输出(stdout)。

img

我们可以修改 server.js,增加对 stdin 的处理,这样就能实现一个最基础的 AI 对话场景:

// server.js

process.stdin.on('data', (data) => {
  const resp = `AI 复述: ${data.toString().trim()}`
  process.stdout.write(resp + '\n')
})

img

当然,除了终端,我们也可以使用程序来自行实现 client 的逻辑。例如:

// client.js

import { spawn } from 'child_process'

// 启动服务端进程
const serverProcess = spawn('node', ['server.js'])

// 监听服务端进程的标准输出
serverProcess.stdout.on('data', (data) => {
  console.log(data.toString().trim())
})

// 测试消息发送
const messages = [
  "明月几时有?",
  "把酒问青天。",
  "不知天上宫阙,",
  "今夕是何年。"
]

messages.forEach((message, index) => {
  setTimeout(() => {
    console.log(`-->:${message}`)
    serverProcess.stdin.write(message + '\n')
  }, index * 1000) // 每秒发送一条数据
})

img

JSON-RPC

JSON-RPC,一种轻量级的远程过程调用(Remote Procedure Call,简称 RPC)协议,它使用 JSON(JavaScript Object Notation) 作为数据格式,用于客户端与服务器之间的通信。一个标准 MCP 应用,其传输数据的规范,就是 JSON-RPC。

JSON-RPC 定义了一套规则,让调用者可以通过发送一个请求,来申请服务器执行对应的函数,再把结果返回给调用者。

数据格式

假设服务器端提供了两个可供外部调用的方法:

// server.js

import fs from 'fs'

export default {
  // 方法 1:求和
  sum({ a, b }) {
    return a + b
  },
  // 方法 2:创建文件
  createFile({ filename, content }) {
    try {
      fs.writeFileSync(filename, content)
      return `文件 ${filename} 已创建`
    } catch (err) {
      return `创建文件 ${filename} 失败:${err.message}`
    }
  }
}

作为客户端,我们希望调用其中的求和方法,发起的请求消息格式如下:

{
  "jsonrpc": "2.0",
  "method": "sum",
  "params": {
    "a": 2,
    "b": 3
  },
  "id": 1
}
字段名 必填 说明
jsonrpc 协议版本,固定为 “2.0”(目前主流都用 2.0)
method 要调用的远程方法/函数名,比如 “sum”
params 传给这个方法的参数,可以是数组或对象,比如 {“a”:2,”b”:3}
id 请求的唯一标识符,用于匹配请求和响应,可以是数字或字符串,自行构建

当服务器处理完请求后,会返回类似如下的响应结果:

{
  "jsonrpc": "2.0",
  "result": 5,
  "id": 1
}
字段名 说明
jsonrpc 协议版本,同样是 “2.0”
result 方法执行后返回的结果,这里是 2 + 3 = 5
id 和请求中的 id一致,用于标识是哪个请求的响应

如果服务器在执行过程中报错,则会返回:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32601,
    "message": "Method not found"
  },
  "id": 1
}
字段 说明
error 是一个对象,包含错误详情
code 错误码,比如 -32601表示“方法未找到”(这是 JSON-RPC 标准错误码之一)
message 错误的可读描述
id 和请求中的 id 对应,用来匹配是哪个请求出错了

场景举例

我们将上述示例构建一个标准的服务器并启动:

// util.js

import fs from 'fs'

export default {
  // 方法 1:求和
  sum({ a, b }) {
    return a + b
  },
  // 方法 2:创建文件
  createFile({ filename, content }) {
    try {
      fs.writeFileSync(filename, content)
      return `文件 ${filename} 已创建`
    } catch (err) {
      return `创建文件 ${filename} 失败:${err.message}`
    }
  }
}

//=====================================================

// server.js

import utils from './utils.js'

process.stdin.on('data', (data) => {
  // 解析 stdin 输入的内容
  const req = JSON.parse(data)
  // 提取请求消息体中指定的调用方法名
  const funcName = req.method
  // 提取请求消息体中调用方法所提供的参数
  const params = req.params
  // 在工具库中调用对应的方法并获得结果
  const result = utils[funcName](params)

  const resp = {
    jsonrpc: '2.0',
    id: req.id,
    result
  }

  process.stdout.write(JSON.stringify(resp) + '\n')
})

MCP

MCP(Model Context Protocol,模型上下文协议)的本质,就是规定了一个应用程序之间如何通信的标准协议。

AI 大模型作为人工智能应用程序,可以使用 MCP ,连接到数据源、工具以及工作流程,从而能够访问关键信息并执行任务。

MCP 使用 JSON-RPC 来编码消息。该协议目前定义了两种用于客户端-服务器通信的标准传输机制:

  • stdio,标准输入和标准输出的通信(推荐,高效、简洁、本地)
  • Streamable HTTP(可远程)

协议明确标注,客户端应该在尽可能的情况下支持 stdio。

基本规范(Lifecycle)

MCP 定义了严格的生命周期:

  1. Initialization(初始化):功能协商和协议版本协议
  2. Operation(操作):正常协议通信
  3. Shutdown(关闭):连接的优雅终止

img

初始化阶段

这个阶段必须是客户端和服务器之间的第一个交互。在此阶段,客户端和服务器:

  • 建立协议版本兼容性
  • 交换和协商功能
  • 共享实现细节
1. Initialization

客户端必须发送一个 initialize 请求来启动此阶段,该请求包括:

  • 支持的协议版本
  • 客户端功能
  • 客户端实现信息
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize", // 值固定
  "params": {
    "protocolVersion": "2025-06-18", // 协议版本需一致
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {},
      "elicitation": {}
    },
    "clientInfo": { // 告知客户端信息
      "name": "warp terminal client",
      "title": "warp terminal",
      "version": "1.0.0"
    }
  }
}
服务器必须响应其自身功能和信息:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "mcp-demo-server",
      "version": "1.0.0"
    }
  }
}
初始化成功后,客户端必须发送 initialized 通知,以表示它已准备好开始正常操作:
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}
2. tools/list

关于 Tools 列表相关的消息结构内容,可参考:Server Features – Tools

客户端可以发送请求,来查询服务器上有哪些工具函数可以供客户端调用

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}
服务器收到请求后的响应:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "sum",
        "title": "两数求和",
        "description": "计算两个数字的和",
        "inputSchema": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number",
              "description": "第一个数字"
            },
            "b": {
              "type": "number",
              "description": "第二个数字"
            }
          },
          "required": [
            "a",
            "b"
          ]
        }
      },
      {
        "name": "createFile",
        "title": "创建文件",
        "description": "创建一个文本文件",
        "inputSchema": {
          "type": "object",
          "properties": {
            "filename": {
              "type": "string",
              "description": "文件名(如:note.txt)"
            },
            "content": {
              "type": "string",
              "description": "文件内容"
            }
          },
          "required": [
            "filename",
            "content"
          ]
        }
      }
    ]
  }
}

操作阶段

在操作阶段,客户端和服务器根据协商的能力交换消息。在符合协议版本的基础上,客户端必须仅调用协商成功的能力。调用方式使用 tools/call。

客户端调用请求:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "sum",
    "arguments": {
      "a": 2,
      "b": 3
    }
  }
}

服务器会在调用成功后,将处理结果返回:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "计算结果:2 + 3 = 5"
      }
    ]
  }
}

MCP Inspector

MCP Inspector 是一个用于测试和调试MCP服务器的交互式开发工具。我们可以用其来连接测试基本规范中构建的 MCP server。

img

MCP SDK

日常开发中,我们可以使用官方提供的 MCP SDK 来进行 MCP 相关的开发。

上述的全生命周期流程的代码用 SDK 重构后:

// server.js

import { McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建 MCP 服务器
const server = new McpServer({
  name: "mcp-sdk-server",
  version: "1.0.0"
});

// 注册工具
server.registerTool("sum",
  {
    title: '两数求和',
    description: '计算两个数字的和',
    inputSchema: {
      a: z.number().describe('第一个数字'),
      b: z.number().describe('第二个数字')
    }
  },
  async ({ a, b }) => ({
    content: [{
      type: "text",
      text: String(`计算结果:${a} + ${b} = ${a + b}`)
    }]
  })
)

server.registerTool("createFile",
  {
    title: '创建文件',
    description: '创建一个文本文件',
    inputSchema: {
      filename: z.string().describe('文件名(如:note.txt)'),
      content: z.string().describe('文件内容')
    }
  },
  async ({ filename, content }) => {
    const fs = await import('fs/promises');
    try {
      await fs.writeFile(filename, content);
      return {
        content: [{ type: "text", text: `文件 "${filename}" 创建成功,内容已写入。` }]
      }
    } catch (err) {
      return {
        content: [{ type: "text", text: `创建文件 "${filename}" 失败:${err.message}` }]
      }
    }
  }
)

// 创建 stdio 传输层,正确传入 stdin 和 stdout
const transport = new StdioServerTransport();

// 连接服务器和传输层
await server.connect(transport);

需要留意,在 tools 的相关说明中,MCP SDK 明确需要使用 zod 作为数据格式校验工具。请确保同 SDK 一同安装。

npm install @modelcontextprotocol/sdk, zod

对接 AI 应用程序

所有能与大模型交互的应用,都可以看作是 AI 应用程序。可以说,AI 应用程序是 AI 智能体的超集。

img

实战演练

我们可以选择 Trae 来对接上一步开发好的 MCP 服务。

{
  "mcpServers": {
    "MCP-SDK-服务器": {
      "command": "node",
      "args": [
        "/Users/gerald/Develop/Projects/Study/mcp-tech/mcp-sdk-demo/server.js"
      ]
    }
  }
}

img

配置成功后,我们可以看到 Trae 会自动拉取 MCP 服务所支持的 tools。

img

现在大模型会在解决我们问题的过程中,自行从 tools 选择并调用。

img

对于创建文件工具的测试调用:

img

其他补充

除了 MCP Server 以外,还有两个核心概念需要了解:

  • MCP Host:协调和管理一个或多个 MCP 客户端的 AI 应用
  • MCP Client:一个维护与 MCP 服务器连接的组件,并从 MCP 服务器获取上下文供 MCP 主机使用

MCP 遵循 Client – Server 架构,其中 MCP Host——如 Claude Code 或 Trae 或 活字格 等 AI 应用——建立与一个或多个 MCP Server 的连接。MCP Host 通过为每个 MCP Server 创建一个 MCP Client 来实现这一点。每个 MCP Client 与其对应的 MCP Server 保持一对一的专用连接。

MCP 相关资源

场景示例代码

Github: https://github.com/GerZhang/course-mcp-tech

MCP和 FunctionCalling的区别

既然有了 MCP,那么 FunctionCalling 是不是就没有价值了?

要解答这个问题,我们还是需要回到本质上去分析。下图是一个查询天气的标准对话,使用的应用程序是 ChatGPT Desktop。

img

用户询问纽约的天气,ChatGPT 通过调用美国气象局的 API 查询结果并进行答复。借助于这个基础场景,我们可以看下,用户通过 AI 大模型调用外部能力获得需求结果的链路是怎么样的:

img

这里有两个概念,我们需要明确:

  • 我们正常认知的大模型,其实是由大模型 API 和模型本身组成。OpenAI 的服务对接的也仅仅是大模型 API。模型本身与外部的所有交互,统一通过 API 进行。当然,模型本身的相关研究完全由大模型厂商负责,开发者一般都不会关注,所以一般大家默认大模型就是 API 和模型本身的聚合体。
  • 我们正常理解的 AI 大模型服务,同样是由大模型和与其配套的标准服务(通常由大模型厂商提供,也可以自行开发)整合在一起的集成服务,例如这个场景所使用的 ChatGPT,用户直接对接的反而是由 OpenAI 提供的标准对外服务,GPT 仅和 OpenAI 的服务进行沟通。

在这个链路中,查询天气函数这样的具体功能,其本质就是函数,而 OpenAI 服务器,则扮演着中间人的角色,作为大模型的代理人,这个服务既负责和最终用户沟通,也负责对接和管理各种各样的工具函数。如果我们希望能够在 AI 侧做更多的工作,就需要将工作中心放在 AI 服务器的开发和管理上。

了解了调用链路后,我们结合 MCP 与 FunctionCalling 的定义,就能很好的理解二者的价值点与区别。

  • FunctionCalling 就是模型调用函数的一种能力。老实说,这个能力的命名很容易造成歧义。大模型本身并不会也不能调用外部函数。大模型只能判断什么样的场景,需要调用哪些函数而已。当大模型判断出结果后,会给出调用意图。具体的调用动作会由 AI 服务服务器来执行。AI 服务器调用完成后,再将结果返回给大模型,由大模型进行二次组装并返回最终答复结果。

img

  • MCP 本质上就是一套函数的发现和调用协议,它只作用在服务器和函数之间。 和大模型一点关系都没有,尽管这个协议名称中包含了 Modle 这个单词,但没有大模型这个协议一样可以正常使用。它提供的所有行为都在 AI 服务器上进行,旨在维护一个更加完善且丰富的上下文(context)。AI 服务器通过 MCP 来维护工具箱,以及执行内部的工具调用。然后通过 API 和大模型进行沟通。

img

此时,我们发现,MCP 和 FunctionCalling 并不重叠,二者分别作用在链路的两个环节,发挥的作用也各不相同。

总结

MCP 并非一项革命性技术,甚至严格来说,它本身并算是一种”技术”。尽管被称为”协议”,却更像是一种约定俗成的规范化接口标准。事实上,在 MCP 出现之前,大语言模型(LLM)与各类 AI 工具或服务之间的协作早已存在——开发者完全可以手动对接 API、定制调用逻辑、处理上下文传递,完成今天 MCP 所支持的各类任务。

但问题在于,这种”能干”往往代价高昂:每个服务都有自己的调用格式、认证方式和上下文管理逻辑,开发者需要为不同工具重复造轮子;模型与工具之间的信息传递缺乏统一语义,容易出错且难以调试;生态之间互不兼容,限制了组合创新的可能性。

MCP 的价值,正是在这些”能干但不好干”的地方体现出来。它通过定义一套轻量、通用、可扩展的协议,让 LLM 与外部服务的对接变得更简单、更一致、更可靠。它没有带来全新的能力,却显著降低了协同成本,提升了开发效率和系统稳定性。

因此,我们应当以理性务实的态度看待 MCP:它不是魔法,而是一种务实的工程共识,是一种推动行业标准化、促进生态协同的重要基础设施。理解它、善用它,才能在日益复杂的 AI 应用生态中游刃有余。

扩展链接

低代码+MCP实战三大案例,企业如何通过MCP构建专属AI智能体?(上)

低代码+MCP实战案例,企业如何通过MCP构建专属AI智能体?(下)

                                                                                </div>



Source link

未经允许不得转载:紫竹林-程序员中文网 » MCP:AI 应用与外部工具协同的标准化协议解析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
关于我们 免责申明 意见反馈 隐私政策
程序员中文网:公益在线网站,帮助学习者快速成长!
关注微信 技术交流
推荐文章
每天精选资源文章推送
推荐文章
随时随地碎片化学习
推荐文章
发现有趣的